﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LGPeWJNM.GilletJohnson
{
    public class GilletJohnson: BaseClasses.IClusterizacao
    {
        /// <summary>
        /// Designa os pontos de acordo com as medianas recebidas
        /// </summary>
        /// <remarks>Implementado pelo método de <i>Gillet e Johnson</i></remarks>
        /// <param name="medianas">Medianas a serem utilizadas para designar os clientes</param>
        /// <param name="destinos">Clientes a serem designados as medianas entradas</param>
        /// <param name="deposito">Coordenadas do depósito</param>
        /// <param name="capacidadeVeiculo">Capacidade de carga de cada veículo</param>
        /// <returns>Conjunto de grupos de clientes</returns>
        public IEnumerable<IEnumerable<BaseClasses.ICliente>> Processar(IEnumerable<BaseClasses.Point> medianas, IEnumerable<BaseClasses.ICliente> destinos, BaseClasses.Point deposito, float capacidadeVeiculo)
        {
            var mMedianas = medianas.Select(s => new mMediana(s, capacidadeVeiculo)).ToList();
            var naoAnalisados = destinos.ToList();

            var aDesignar = from n in naoAnalisados/*.AsParallel()*/
                            from m in mMedianas.AsParallel()
                            group new { mediana = m, dist = m % n.Coordenada } by n into g
                            select new { cliente = g.Key, razao = g.Select(s => s.dist).OrderBy(o => o).Take(2).Min() / g.Select(s => s.dist).OrderBy(o => o).Take(2).Max() } into d
                            orderby d.razao
                            select d;
            foreach (var designando in aDesignar)
            {
                try
                {
                    var medianaProxima = (from m in mMedianas/*.AsParallel()*/
                                          orderby m % designando.cliente.Coordenada
                                          where m.CapacidadeDisponivel >= designando.cliente.Demanda
                                          select m).AsParallel().First();
                    if (medianaProxima.CapacidadeDisponivel >= designando.cliente.Demanda)
                    {
                        medianaProxima.Clientes.Add(designando.cliente);
                        naoAnalisados.Remove(designando.cliente);
                    }
                }
                catch (System.InvalidOperationException)
                {
                    throw new ArgumentOutOfRangeException("Um cliente não pode ser alocado, pois não há uma mediana mais próxima com capacidade disponível.");
                }
            }

            //validando a consistencia da designação
            var medianasEstouradas = from m in mMedianas
                                     where m.CapacidadeDisponivel < 0
                                     orderby m.CapacidadeDisponivel
                                     select m;
            if (medianasEstouradas.Count() > 0)
            {
                throw new ArgumentOutOfRangeException("Houve super alocação de uma mediana.");
            }

            return mMedianas.Select(s => s.Clientes);
        }
    }
}
